#include "form_self_checking.h"
#include "ui_form_self_checking.h"

// add necessary includes here
#include "ui/share/form_shield_dlg.h"
#include "ui/share/form_prompt_dlg.h"
#include "utils/configure/configure_bll.h"

static QElapsedTimer g_etTimer;
#define DF_ELAPSED_SLEEP(tm) \
    g_etTimer.start(); \
    while(g_etTimer.elapsed() < tm) { QCoreApplication::processEvents(); QThread::msleep(100); }\

/*
 * Function:    FormSelfChecking
 * Description: The default constructor
 * Paramer:     void
 * Return:
 */
FormSelfChecking::FormSelfChecking(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::FormSelfChecking)
{
    qDebug() << Q_FUNC_INFO;

    ui->setupUi(this);
}

/*
 * FormHistoryResult:    ~FormSelfChecking
 * Description: The default destructor
 * Paramer:     void
 * Return:
 */
FormSelfChecking::~FormSelfChecking()
{
    qDebug() << Q_FUNC_INFO;

    delete ui;
}

/*
 * Function:    getInstance
 * Description: Get an instance of "FormSelfChecking" class, if exist, return it
 * Paramer:     QWidget *pParent - the pointer of QWidget class
 * Return:      the pointer of "FormSelfChecking" class
 */
FormSelfChecking* FormSelfChecking::getInstance(QWidget *pParent)
{
    static FormSelfChecking *pWidget = nullptr;
    if(!pWidget) {
        // Create a new object if it doesn't exist
        pWidget = new FormSelfChecking(pParent);
        if(nullptr == pWidget) return nullptr;

        // After constructing object, initialize the object
        pWidget->_initObj();

        // Init the window style
        pWidget->_initUi();
    }

    // Set the parent object
    if(pParent) pWidget->setParent(pParent);

    return pWidget;
}

/*
 * Function:    _initObj
 * Description: After constructing object, initialize the object
 * Paramer:     void
 * Return:      void
 */
void FormSelfChecking::_initObj()
{
    qDebug() << Q_FUNC_INFO;

    if(!m_pMaintainTV) m_pMaintainTV = new MaintainTableView(this);
    Q_ASSERT(NULL != m_pMaintainTV);

    m_pMaintainTV->setFixedHeight(355);
    ui->formLayout_CheckingList->addWidget(m_pMaintainTV);

    // Signal-slot initialization
    std::vector<bool> vecConnect;
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalRefreshUI()), this, SLOT(slotRefreshUI()), static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(m_pMaintainTV, SIGNAL(signalButtonClicked(int, bool)), this, SLOT(slotButtonClicked(int, bool))));
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalUpdateProgressBarValue(int, int)), this, SLOT(slotUpdateProgressBarValue(int, int)), static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalUpdateResult(int, int, QString)), this, SLOT(slotUpdateResult(int, int, QString)), static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)));
    QObject::connect(this, SIGNAL(signalDisplayTransparentDlg(bool)), FormShieldDlg::getInstance(this), SIGNAL(signalDisplayWindow(bool)));
    for(int i=0; i<(int)vecConnect.size(); i++) {
        Q_ASSERT(vecConnect.at(i));
    }
}

/*
* Function:		_initUi
* Description:	Init the window style
* Paramer:		void
* Return:		void
*/
void FormSelfChecking::_initUi()
{
    qDebug() << Q_FUNC_INFO;

    // Displays the background image of the window
    QWidget::setAttribute(Qt::WA_StyledBackground);

    SET_STYLE_SHEET(QString(":/%1/factory/self-checking/%2.qss").arg("skin_1").arg(this->objectName()));

    ui->pushButton_Start->setFocusPolicy(Qt::NoFocus);
}

/*
* Function:		changeEvent
* Description:	Triggers an event before it is switch language
* Paramer:		QShowEvent *event - the pointer of "QEvent"
* Return:		void
*/
void FormSelfChecking::changeEvent(QEvent *e)
{
    switch (e->type()) {
    case QEvent::LanguageChange:
    {
        ui->retranslateUi(this);
        qDebug() << Q_FUNC_INFO;
        break;
    }
    default:
        break;
    }

    QWidget::changeEvent(e);
}

void FormSelfChecking::on_pushButton_Start_clicked()
{
    // Refresh the view
    m_pMaintainTV->ResetView();

    _procDetectAll();

    qDebug() << Q_FUNC_INFO;
}

/*
* Function:		slotRefreshUI
* Description:	The slot that refresh UI info
* Paramer:		void
* Return:		void
*/
void FormSelfChecking::slotRefreshUI()
{
    qDebug() << Q_FUNC_INFO;
}

/*
* Function:		slotButtonClicked
* Description:	Exec an test item after click
* Paramer:		int nRowIdx - the row index (test item)
*               bool bAction - is action ?
* Return:		void
*/
void FormSelfChecking::slotButtonClicked(int nRowIdx, bool bAction)
{
    qDebug() << Q_FUNC_INFO << nRowIdx << bAction;
    switch (nRowIdx) {
    case EM_MI_CAN: { _procDetectCAN(CanBLL_T1C::getInstance(this)); break; }
    case EM_MI_USB: { _procDetectUSB(); break; }
    case EM_MI_BUZZER: { _procDetectBuzzer(FirmwareBLL::getInstance(this)); break; }
    case E_MI_PIPETTE: { _procDetectPipette(CanBLL_T1C::getInstance(this)); break; }
    case E_MI_MOTION: { _procDetectMotion(CanBLL_T1C::getInstance(this)); break; }
    case E_MI_HEATING: { _procDetectHeating(CanBLL_T1C::getInstance(this)); break; }
    default: break;
    }
}

/*
* Function:		_ProcDetectAll
* Description:	Deal with all device
* Paramer:		void
* Return:		void
*/
void FormSelfChecking::_procDetectAll()
{
    qDebug() << Q_FUNC_INFO;

    ui->pushButton_Start->setText(tr("Stop"));

    _procDetectCAN(CanBLL_T1C::getInstance(this));

    _procDetectUSB();

    _procDetectBuzzer(FirmwareBLL::getInstance(this));

    _procDetectPipette(CanBLL_T1C::getInstance(this));

    _procDetectMotion(CanBLL_T1C::getInstance(this));

    _procDetectHeating(CanBLL_T1C::getInstance(this));

    ui->pushButton_Start->setText(tr("Start"));
}

/*
* Function:		_procDetectCAN
* Description:	Deal with detect CAN device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfChecking::_procDetectCAN(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    emit signalDisplayTransparentDlg(true);

    if(pSerialbusBLL) {
        emit signalUpdateProgressBarValue(EM_MI_CAN, -1);
        DF_ELAPSED_SLEEP(500);

        STC_VERSION_INFO stVerInfo[3];

        do{
            emit signalUpdateProgressBarValue(EM_MI_CAN, 10);
            DF_ELAPSED_SLEEP(500);

            // Select the firmware version info
            QList<unsigned int> lstCanID = QList<unsigned int>() << CanVars::gs_nIapCanID_CH1 << CanVars::gs_nPipetteCanID_CH1 << CanVars::gs_nHeatIapCanID_CH1;

            const int nListSize = 3;
            if(nListSize == lstCanID.size()) {
                for(int i=0; i<nListSize; i++) {
                    stVerInfo[i].clear();

                    int nErrCode = pSerialbusBLL->getIapVersion(stVerInfo[i], lstCanID.at(i));
                    if(0 == nErrCode) {
                        qDebug() << Q_FUNC_INFO << i << nErrCode << stVerInfo[i].sSerialNumber << stVerInfo[i].sFirmwareVer;
                    }

                    if(stVerInfo[i].sFirmwareVer.trimmed().isEmpty()) {
                        nRetVal = - (i+1);
                        goto do_exit;
                    }
                }
            }

            emit signalUpdateProgressBarValue(EM_MI_CAN, 50);
            DF_ELAPSED_SLEEP(500);

        }while(0);


        emit signalUpdateProgressBarValue(EM_MI_CAN, 100);
        DF_ELAPSED_SLEEP(500);

        if(0 == nRetVal) {
            emit signalUpdateResult(EM_MI_CAN, nRetVal, "√");
        }
        else {
do_exit:
            emit signalUpdateResult(EM_MI_CAN, nRetVal, "×");
        }
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectUSB
* Description:	Deal with detect USB device
* Paramer:		void
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfChecking::_procDetectUSB()
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    QByteArray baOutput;

    emit signalDisplayTransparentDlg(true);

    emit signalUpdateProgressBarValue(EM_MI_USB, -1);
    DF_ELAPSED_SLEEP(500);

    do{
        emit signalUpdateProgressBarValue(EM_MI_USB, 50);
        DF_ELAPSED_SLEEP(500);

        QProcess procFDisk;
        procFDisk.start("sh", QStringList() << "-c" << "/sbin/fdisk -l | grep sda1 |  awk '{print $1}'");
        procFDisk.waitForFinished(2000);
        baOutput = procFDisk.readAllStandardOutput();
        qDebug() << Q_FUNC_INFO << baOutput;
    }while(0);


    emit signalUpdateProgressBarValue(EM_MI_USB, 100);
    DF_ELAPSED_SLEEP(500);

    if(0 < baOutput.size()) {
        emit signalUpdateResult(EM_MI_USB, nRetVal, "√");
    }
    else {
        nRetVal = -1;
        emit signalUpdateResult(EM_MI_USB, nRetVal, "×");
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectBuzzer
* Description:	Deal with detect buzzer device
* Paramer:		FirmwareBLL *pFirmwareBLL - the pointer of FirmwareBLL structure
* Return:		void
*/
int FormSelfChecking::_procDetectBuzzer(FirmwareBLL *pFirmwareBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal;

    emit signalDisplayTransparentDlg(true);

    emit signalUpdateProgressBarValue(EM_MI_BUZZER, -1);
    DF_ELAPSED_SLEEP(500);

    do{
        emit signalUpdateProgressBarValue(EM_MI_BUZZER, 10);
        DF_ELAPSED_SLEEP(500);

        // Select the firmware version info
        nRetVal = pFirmwareBLL->debugBuzzer(1);

        emit signalUpdateProgressBarValue(EM_MI_BUZZER, 50);
        DF_ELAPSED_SLEEP(500);

    }while(0);

    emit signalUpdateProgressBarValue(EM_MI_BUZZER, 100);
    DF_ELAPSED_SLEEP(500);

    if(0 == nRetVal) {
        emit signalUpdateResult(EM_MI_BUZZER, nRetVal, "√");
    }
    else {
        nRetVal = -1;
        emit signalUpdateResult(EM_MI_BUZZER, nRetVal, "×");
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectPipette
* Description:	Deal with detect pipette device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfChecking::_procDetectPipette(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    emit signalDisplayTransparentDlg(true);

    if(pSerialbusBLL) {
        emit signalUpdateProgressBarValue(E_MI_PIPETTE, -1);
        DF_ELAPSED_SLEEP(500);

        do{
            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 10);
            DF_ELAPSED_SLEEP(500);

            // Step 1: Reset all
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_TP_SRST, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 30);
            DF_ELAPSED_SLEEP(500);

            // Step 2: Downwards
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_ABS_MOVE, QList<int>() << MotorVars_T1C::nPierceOffsetY / 2);
            if(0 != nRetVal) {
                nRetVal = -2;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 40);
            DF_ELAPSED_SLEEP(500);

            // Step 3: Spit
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_SPIT, QList<int>() << MotorVars_T1C::nTipsMaxVolume);
            if(0 != nRetVal) {
                nRetVal = -3;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 50);
            DF_ELAPSED_SLEEP(500);

            // Step 4: Suction
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_SUCTION, QList<int>() << MotorVars_T1C::nTipsMaxVolume);
            if(0 != nRetVal) {
                nRetVal = -4;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 60);
            DF_ELAPSED_SLEEP(500);

            // Step 5: Upwoards
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_PA_ABS_MOVE, QList<int>() << MotorVars_T1C::nPierceOffsetY2);
            if(0 != nRetVal) {
                nRetVal = -5;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_PIPETTE, 70);
            DF_ELAPSED_SLEEP(500);
        }while(0);

        emit signalUpdateProgressBarValue(E_MI_PIPETTE, 100);
        DF_ELAPSED_SLEEP(500);

        if(0 == nRetVal) {
            emit signalUpdateResult(E_MI_PIPETTE, nRetVal, "√");
        }
        else {
do_exit:
            emit signalUpdateResult(E_MI_PIPETTE, nRetVal, "×");
            pSerialbusBLL->doMethodDebug(CanVars::gs_nPipetteCanID_CH1, E_TP_SRST, QList<int>());
        }
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectMotion
* Description:	Deal with detect motor device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfChecking::_procDetectMotion(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;

    emit signalDisplayTransparentDlg(true);

    if(pSerialbusBLL) {
        emit signalUpdateProgressBarValue(E_MI_MOTION, -1);
        DF_ELAPSED_SLEEP(500);

        do{
            emit signalUpdateProgressBarValue(E_MI_MOTION, 10);
            DF_ELAPSED_SLEEP(500);

            // Step 1: Reset all
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 30);
            DF_ELAPSED_SLEEP(500);

            // Step 2: Exit cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_QUIT, QList<int>() << MotorVars_T1C::nGrapSleeveOffsetX);
            if(0 != nRetVal) {
                nRetVal = -2;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 40);
            DF_ELAPSED_SLEEP(500);

            // Magnetic sleeve magnetic rod down
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_LKDO, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY / 5);
            if(0 != nRetVal) {
                nRetVal = -3;
                goto do_exit;
            }

            // Magnetic rod motor up
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SKUP, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY / 5);
            if(0 != nRetVal) {
                nRetVal = -4;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 50);
            DF_ELAPSED_SLEEP(500);

            // Magnetic sleeve mixing
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_BUBB, QList<int>() << 25600 << 1 << 500);
            if(0 != nRetVal) {
                nRetVal = -5;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 60);
            DF_ELAPSED_SLEEP(500);

            // Magnetic sleeve up
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SLUP, QList<int>() << MotorVars_T1C::nMixingSleeveOffsetY / 5);
            if(0 != nRetVal) {
                nRetVal = -6;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 70);
            DF_ELAPSED_SLEEP(500);

            // Step 3: Load cartridge
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_LOAD, QList<int>() << MotorVars_T1C::nGrapSleeveOffsetX);
            if(0 != nRetVal) {
                nRetVal = -7;
                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_MOTION, 80);
            DF_ELAPSED_SLEEP(500);

        }while(0);

        emit signalUpdateProgressBarValue(E_MI_MOTION, 100);
        DF_ELAPSED_SLEEP(500);

        if(0 == nRetVal) {
            emit signalUpdateResult(E_MI_MOTION, nRetVal, "√");
        }
        else {
do_exit:
            emit signalUpdateResult(E_MI_MOTION, nRetVal, "×");

            pSerialbusBLL->doMethodDebug(CanVars::gs_nIapCanID_CH1, E_TP_SRST, QList<int>());
        }
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
* Function:		_procDetectHeating
* Description:	Deal with detect heating device
* Paramer:		CanBLL_T1C *pSerialbusBLL - the pointer of CanBLL_T1C structure
* Return:		int - the error code, 0:success; otherwise: failure
*/
int FormSelfChecking::_procDetectHeating(CanBLL_T1C *pSerialbusBLL)
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = 0;
    QList<float> lstTempVal;

    emit signalDisplayTransparentDlg(true);

    if(pSerialbusBLL) {
        emit signalUpdateProgressBarValue(E_MI_HEATING, -1);
        DF_ELAPSED_SLEEP(500);

        do{
            emit signalUpdateProgressBarValue(E_MI_HEATING, 10);
            DF_ELAPSED_SLEEP(500);

            // Step 1: disable heat on channels
            nRetVal = pSerialbusBLL->doMethodDebug(CanVars::gs_nHeatIapCanID_CH1, HT_HA_HTSP, QList<int>());
            if(0 != nRetVal) {
                nRetVal = -1;
                goto do_exit;
            }

            // sleep 10s
            emit signalUpdateProgressBarValue(E_MI_HEATING, 50);
            DF_ELAPSED_SLEEP(500);

            // Step 2: read temperature on channels
            nRetVal = pSerialbusBLL->getChannelTemperature(lstTempVal);
            if(0 != nRetVal) {
                if(DEF_HEAT_CHANNEL_SIZE + 1 == lstTempVal.size()) {
                    nRetVal = -10;
                    int nErrCH = 0x0;
                    for(int i=0; i<DEF_HEAT_CHANNEL_SIZE; i++) {
                        if(10 > lstTempVal[i + 1]) {
                            nErrCH |= (0x1 << i);
                        }
                    }

                    if(0 < nErrCH) {
                        nRetVal += -nErrCH;
                    }
                }
                else {
                    nRetVal = -2;
                }

                goto do_exit;
            }

            emit signalUpdateProgressBarValue(E_MI_HEATING, 80);
            DF_ELAPSED_SLEEP(500);

        }while(0);

        emit signalUpdateProgressBarValue(E_MI_HEATING, 100);
        DF_ELAPSED_SLEEP(500);

        if(0 == nRetVal) {
            emit signalUpdateResult(E_MI_HEATING, nRetVal, "√");
        }
        else {
do_exit:
            emit signalUpdateResult(E_MI_HEATING, nRetVal, "×");
        }
    }

    emit signalDisplayTransparentDlg(false);

    qDebug() << Q_FUNC_INFO << nRetVal << lstTempVal;
    return nRetVal;
}

void FormSelfChecking::slotUpdateProgressBarValue(int nRowIdx, int nValue)
{
    qDebug() << Q_FUNC_INFO << nRowIdx << nValue;

    MaintainTableModel *pModel = m_pMaintainTV->tableModel();
    QVector<QStringList>& vecRowData = pModel->DataVector();

    if(0 < nValue) {
        if(100 <= nValue) nValue = 100;
        vecRowData[nRowIdx][1] = QString::number(nValue);

        if(100 == nValue) {
            emit signalProgressFinished(nRowIdx);
        }
    }
    else {
        vecRowData[nRowIdx][1] = "0";
        vecRowData[nRowIdx][2] = "";
        vecRowData[nRowIdx][3] = "";
    }

    emit pModel->layoutChanged();
}

void FormSelfChecking::slotUpdateResult(int nRowIdx, int nErrCode, QString sResult)
{
    qDebug() << Q_FUNC_INFO << nRowIdx << nErrCode << sResult;

    MaintainTableModel *pModel = m_pMaintainTV->tableModel();
    QVector<QStringList>& vecRowData = pModel->DataVector();

    vecRowData[nRowIdx][2] = QString::number(nErrCode);
    vecRowData[nRowIdx][3] = sResult;

    emit pModel->layoutChanged();
}
